package ar.edu.unlu.sistemas.p2p.business.index;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import ar.edu.unlu.sistemas.p2p.android.sql.IndexOpenHelper;
import ar.edu.unlu.sistemas.p2p.business.util.Constants;
import ar.edu.unlu.sistemas.p2p.business.util.Logger;
import ar.edu.unlu.sistemas.p2p.business.util.Params;
import ar.edu.unlu.sistemas.p2p.business.util.StringUtils;
import ar.edu.unlu.sistemas.p2p.business.vo.DocumentVO;
import ar.edu.unlu.sistemas.p2p.business.vo.QueryProcessVO;
import ar.edu.unlu.sistemas.p2p.business.vo.QueryResultVO;
import ar.edu.unlu.sistemas.p2p.business.vo.TermFrecuencyVO;
import ar.edu.unlu.sistemas.p2p.business.vo.WordVO;

public class IndexController {
	
	private static IndexController INSTANCE;
	private IndexOpenHelper index; 
	private int wordSequence;
	private int docSequence;
	private HashMap<String,WordVO> wordsCache;
	
	private HashMap<String, WordVO> getWordsCache() {
		return wordsCache;
	}

	private void setWordsCache(HashMap<String, WordVO> wordsCache) {
		this.wordsCache = wordsCache;
	}

	private int getWordSequence() {
		return wordSequence;
	}

	private void setWordSequence(int wordSequence) {
		this.wordSequence = wordSequence;
	}

	private void incWordSequence() {
		this.setWordSequence(this.wordSequence +1);
	}
	
	private int getDocSequence() {
		return docSequence;
	}

	private void setDocSequence(int docSequence) {
		this.docSequence = docSequence;
	}
	
	private void incDocumentSequence() {
		this.setDocSequence(this.docSequence +1);
	}

	private IndexController() {
		
	}
	
	public static IndexController getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new IndexController();
		}
		return INSTANCE;
	}

	public IndexOpenHelper getIndex() {
		return index;
	}

	
	/**
	 * Inicia la secuencias de las tablas de Termino y Documento ya que no sabemos si
	 * el motor de BD que utilizamos soporte secuencias. Por eso mismo lo manejamos
	 * por código.
	 */
	public void initSequences() {
		Log.i(this.getClass().getSimpleName(), "Inicio las secuencias de BD");
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.DOC_TABLE_NAME, 
				new String[]{"MAX("+IndexOpenHelper.DOC_ID+")"}, 
				null, 
				null, 
				null, 
				null, 
				null);
		if (cursor.getCount() > 0) {
			cursor.moveToFirst();
			this.setDocSequence(cursor.getInt(0));
		} else {
			this.setDocSequence(0);
		}
		cursor.close();
		cursor = this.index.getDb().query(IndexOpenHelper.WORD_TABLE_NAME, 
				new String[]{"max("+IndexOpenHelper.WORD_ID+")"}, 
				null, 
				null, 
				null, 
				null, 
				null);
		if (cursor.getCount() > 0) {
			cursor.moveToFirst();
			this.setWordSequence(cursor.getInt(0));
		} else {
			this.setWordSequence(0);
		}
		cursor.close();
		this.incDocumentSequence();
		this.incWordSequence();
	}
	
	public synchronized void initCaches() {
		Log.i(this.getClass().getSimpleName(), "Inicializo la cache");
		this.setWordsCache(new HashMap<String,WordVO>());
		Iterator<WordVO> allWords = this.findWords(null,null).iterator();
		WordVO wordVO;
		while (allWords.hasNext()) {
			wordVO = allWords.next();
			this.getWordsCache().put(wordVO.getName(), wordVO);
		}
	}
	
	public synchronized void cleanCaches() {
		this.setWordsCache(new HashMap<String,WordVO>());
	}

	public synchronized void setIndex(IndexOpenHelper index) {
		this.index = index;
		this.initSequences();
		if (Params.USE_WORDS_CACHE) {
			this.initCaches();
		}
	}
	
	//-------------------------------------------------------------------------------------------------------
	//Entidad TERMINO
	//-------------------------------------------------------------------------------------------------------
	
	/**
	 * Agrega una entidad TERMINO a la respectiva tabla con el nombre clave pasado como
	 * parámetro.
	 */
	public synchronized void insertWord(String word) {
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice la palabra "+word);
		ContentValues values = new ContentValues();
		values.put(IndexOpenHelper.WORD_ID, this.getWordSequence());
		values.put(IndexOpenHelper.WORD_KEY, word);
		long r = this.getIndex().getDb().insert(IndexOpenHelper.WORD_TABLE_NAME, IndexOpenHelper.WORD_ID, values);
		if (r != -1) {
			if (Params.USE_WORDS_CACHE) {
				Log.i(this.getClass().getSimpleName(), "Se inserta en la cache");
				this.getWordsCache().put(word, new WordVO(this.getWordSequence(),word));
			}
			this.incWordSequence();
		}
	}
	
	/**
	 * Método genérico para realizar un query que retorna una fila unívoca de la tabla 
	 * de la entidad TERMINO. Se pasa como parámetro la columna por la que se filtra
	 * la búsqueda con el valor indicado.
	 * 
	 * @param column
	 * @param value
	 * @return el value object de la fila TERMINO retornada
	 */
	private WordVO findWord(String column, Object value) {
		Log.i(this.getClass().getSimpleName(), "Se busca la palabra filtrando por columna "+column);
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.WORD_TABLE_NAME, 
				new String[]{IndexOpenHelper.WORD_ID,IndexOpenHelper.WORD_KEY}, 
				column+" = ?", 
				new String[]{value.toString()}, 
				null, 
				null, 
				null);
		WordVO outVO = null;
		if (cursor.getCount() > 0) {
			cursor.moveToFirst();
			outVO = new WordVO();
			outVO.setId(cursor.getInt(0));
			outVO.setName(cursor.getString(1));
		}
		cursor.close();
		return outVO;
	}
	
	/**
	 * Retorna el value object de un TERMINO identificado con la clave indicada como parámetro
	 * 
	 * @param word
	 * @return value object del TERMINO
	 */
	public synchronized WordVO findWordByName(String word) {
		Log.i(this.getClass().getSimpleName(), "Se busca la palabra por nombre "+word);
		if (Params.USE_WORDS_CACHE) {
			return this.findWordByNameWithCache(word);
		} else {
			return this.findWord(IndexOpenHelper.WORD_KEY,word);
		}
	}
	
	/**
	 * Busca la palabra en la caché, la cual esta sincronizada con la tabla de la entidad
	 * 
	 * @param key
	 * @return value-object de término que se busca
	 */
	private WordVO findWordByNameWithCache(String key) {
		Logger.logInfo(this.getClass().getSimpleName(), "Uso la cache");
		WordVO word = this.getWordsCache().get(key);
		if (word == null) {
			word = this.findWord(IndexOpenHelper.WORD_KEY,key);
			if (word != null) {
				Log.i(this.getClass().getSimpleName(), "Se inserta en la cache");
				this.getWordsCache().put(word.getName(), word);
			}
		}
		return word;
	}
	
	/**
	 * Retorna el value object de un TERMINO identificado con el id indicado como parámetro
	 * 
	 * @param id
	 * @return value object del TERMINO
	 */
	public synchronized WordVO findWordById(int id) {
		Log.i(this.getClass().getSimpleName(), "Se busca la palabra por id "+id);
		return findWord(IndexOpenHelper.WORD_ID,id);
	}
	
	/**
	 * Método genérico para retornar múltiples terminos filtrado por las condiciones y valores
	 * pasados como parámetro
	 * 
	 * @param selection
	 * @param selectionArgs
	 * @return colleción de value-objects de entidad TERMINO
	 */
	private Collection<WordVO> findWords(String selection, String[] selectionArgs) {
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.WORD_TABLE_NAME, 
				new String[]{IndexOpenHelper.WORD_ID,IndexOpenHelper.WORD_KEY}, 
				selection, 
				selectionArgs, 
				null, 
				null, 
				null);
		Collection<WordVO> words = new ArrayList<WordVO>();
		if (cursor.getCount() > 0) {
			Log.d(this.getClass().getSimpleName(), "La consulta trajo "+String.valueOf(cursor.getCount())+" filas");
			WordVO wordVO;
			while (cursor.moveToNext()) {
				wordVO = new WordVO();
				wordVO.setId(cursor.getInt(0));
				wordVO.setName(cursor.getString(1));
				words.add(wordVO);
			}
		} else {
			Log.d(this.getClass().getSimpleName(), "La consulta no trajo resultados");
		}
		cursor.close();
		return words;
	}
	
	/**
	 * Retorna todos los TERMINOS existentes en la tabla de la entidad
	 * 
	 * @return value objects de todas las filas de la tabla TERMINO
	 */
	public synchronized Collection<WordVO> findAllWords() {
		Log.i(this.getClass().getSimpleName(), "Busco todas las palabras");
		if (Params.USE_WORDS_CACHE) {
			return this.findAllWordsWithCache();
		} else {
			return this.findWords(null,null);
		}
	}
	
	/**
	 * Retorna todos los TERMINOS existentes en la cache de la entidad
	 * 
	 * @return value objects de todas las filas de la tabla TERMINO
	 */
	private Collection<WordVO> findAllWordsWithCache() {
		Log.i(this.getClass().getSimpleName(), "Uso la cache");
		return this.getWordsCache().values();
	}
	
	/**
	 * Retorna todos los terminos los cuales su nombre este dentro del subconjunto
	 * que se indica como parámetro
	 * 
	 * @param words
	 * @return colección de value objects de entidad TERMINO
	 */
	public synchronized Collection<WordVO> findWordsByNames(Collection<String> words) {
		Log.i(this.getClass().getSimpleName(), "Busco palabras dentro del conjunto "+StringUtils.convertToSQLSet(words));
		String[] strArr = new String[words.size()];
		int i = 0;
		for (Iterator<String> it = words.iterator(); it.hasNext(); ) {
			strArr[i]=it.next();
			i++;
		}
		if (Params.USE_WORDS_CACHE) {
			return this.findWordsByNameWithCache(words);
		} else {
			return this.findWords(IndexOpenHelper.WORD_KEY+" IN "+StringUtils.convertToInterrogativeSQLSet(words.size()),
					strArr);
		}
	}
	
	/**
	 * Retorna todos los terminos en la cache los cuales su nombre este dentro del subconjunto
	 * que se indica como parámetro
	 * 
	 * @param words
	 * @return colección de value objects de entidad TERMINO
	 */
	private Collection<WordVO> findWordsByNameWithCache(Collection<String> words) {
		Log.i(this.getClass().getSimpleName(), "Uso la cache");
		Iterator<WordVO> allWords = this.getWordsCache().values().iterator();
		WordVO wordVO;
		Collection<WordVO> outColl = new ArrayList<WordVO>();
		while (allWords.hasNext()) {
			wordVO = allWords.next();
			if (words.contains(wordVO.getName())) {
				outColl.add(wordVO);
			}
		}
		return outColl;
	}
	
	/**
	 * Retorna todos los terminos que su clave se parece a la indicada
	 * 
	 * @param word
	 * @return colección de value objects de entidad TERMINO
	 */
	public synchronized Collection<WordVO> findWordsLikeName(String word) {
		Log.i(this.getClass().getSimpleName(), "Busco palabras como "+word);
		if (Params.USE_WORDS_CACHE) {
			return this.findWordsLikeNameWithCache(word);
		} else {
			return this.findWords(IndexOpenHelper.WORD_KEY+" LIKE '"+word+"%'",
					null);
		}
	}
	
	/**
	 * Retorna todos los terminos que su clave se parece a la indicada
	 * Busca en la cache
	 * 
	 * @param word
	 * @return colección de value objects de entidad TERMINO
	 */
	private Collection<WordVO> findWordsLikeNameWithCache(String word) {
		Log.i(this.getClass().getSimpleName(), "Uso la cache");
		Iterator<WordVO> allWords = this.getWordsCache().values().iterator();
		WordVO wordVO;
		Collection<WordVO> outColl = new ArrayList<WordVO>();
		while (allWords.hasNext()) {
			wordVO = allWords.next();
			if (wordVO.getName().contains(word)) {
				outColl.add(wordVO);
			}
		}
		return outColl;
	}
	
	public synchronized void deleteWord(String id) {
		this.index.getDb().delete(IndexOpenHelper.WORD_TABLE_NAME, null, null);
	}
	
	//-------------------------------------------------------------------------------------------------------
	//Entidad DOCUMENTO
	//-------------------------------------------------------------------------------------------------------
	
	/**
	 * Inserta un DOCUMENTO con la clave pasada por parámetro
	 */
	public synchronized int insertDoc(String doc, String type) {
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice el documento "+doc);
		ContentValues values = new ContentValues();
		int id = this.getDocSequence();
		values.put(IndexOpenHelper.DOC_ID, id);
		values.put(IndexOpenHelper.DOC_KEY, doc);
		values.put(IndexOpenHelper.DOC_TYPE, type);
		long r = this.getIndex().getDb().insert(IndexOpenHelper.DOC_TABLE_NAME, IndexOpenHelper.DOC_ID, values);
		if (r != -1) {
			this.incDocumentSequence();
		}
		return id;
	}
	
	public synchronized void insertDocImpl(String doc) {
		this.insertDoc(doc, Constants.DOC_TYPE);
	}
	
	public synchronized int insertQueryImpl(String doc)  {
		return this.insertDoc("query"+String.valueOf(this.getDocSequence()), Constants.QUERY_TYPE);
	}
	
	/**
	 * Método genérico para realizar un query que retorna una fila unívoca de la tabla 
	 * de la entidad DOCUMENT. Se pasa como parámetro la columna por la que se filtra
	 * la búsqueda con el valor indicado.
	 * 
	 * @param column
	 * @param value
	 * @return value object del DOCUMENTO obtenido
	 */
	private DocumentVO findDocument(String column, Object value) {
		Log.i(this.getClass().getSimpleName(), "Se busca el documento filtrando por columna "+column);
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.DOC_TABLE_NAME, 
				new String[]{IndexOpenHelper.DOC_ID,IndexOpenHelper.DOC_KEY}, 
				column+" = ?", 
				new String[]{value.toString()}, 
				null, 
				null, 
				null);
		DocumentVO outVO = null;
		if (cursor.getCount() > 0) {
			cursor.moveToFirst();
			outVO = new DocumentVO();
			outVO.setId(cursor.getInt(0));
			outVO.setName(cursor.getString(1));
		}
		cursor.close();
		return outVO;
	}
	
	/**
	 * Método para obtener un DOCUMENTO a partir de su nombre clave
	 * 
	 * @param doc
	 * @return value object del DOCUMENTO obtenido
	 */
	public synchronized DocumentVO findDocumentByName(String doc) {
		Log.i(this.getClass().getSimpleName(), "Se busca el documento por nombre "+doc);
		return findDocument(IndexOpenHelper.DOC_KEY,doc);
	}
	
	/**
	 * Método para obtener un DOCUMENTO a partir de su id
	 * 
	 * @param id
	 * @return value object del DOCUMENTO obtenido
	 */
	public synchronized DocumentVO findDocumentById(int id) {
		Log.i(this.getClass().getSimpleName(), "Se busca el documento por id "+id);
		return findDocument(IndexOpenHelper.DOC_ID,id);
	}
	
	/**
	 * Método genérico para obtener documentos a partir de una condición pasada
	 * por parámetro.
	 * 
	 * @param selection
	 * @param selectionArgs
	 * @return colección de value objects
	 */
	private Collection<DocumentVO> findDocs(String selection, String[] selectionArgs) {
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.DOC_TABLE_NAME, 
				new String[]{IndexOpenHelper.DOC_ID,IndexOpenHelper.DOC_KEY}, 
				selection, 
				selectionArgs, 
				null, 
				null, 
				null);
		Collection<DocumentVO> docs = new ArrayList<DocumentVO>();
		if (cursor.getCount() > 0) {
			Log.d(this.getClass().getSimpleName(), "La consulta trajo "+String.valueOf(cursor.getCount())+" filas");
			DocumentVO docVO;
			while (cursor.moveToNext()) {
				docVO = new DocumentVO();
				docVO.setId(cursor.getInt(0));
				docVO.setName(cursor.getString(1));
				docs.add(docVO);
			}
		} else {
			Log.d(this.getClass().getSimpleName(), "No trajo resultados la consulta");
		}
		cursor.close();
		return docs;
	}
	
	private Collection<DocumentVO>findDocsImpl(String selection, String[] selectionArgs) {
		selection = selection != null ? selection+" AND "+IndexOpenHelper.DOC_TYPE+" = '"+Constants.DOC_TYPE+"'" :IndexOpenHelper.DOC_TYPE+" = '"+Constants.DOC_TYPE +"'";
		return this.findDocs(selection, selectionArgs);
	}
	
	/**
	 * Retorna todos los documentos existentes en la tabla DOCUMENTO
	 * 
	 * @return colección de value objects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findAllDocs() {
		Log.i(this.getClass().getSimpleName(), "Busco todos los documentos");
		return this.findDocsImpl(null,null);
	}
	
	/**
	 * Retorna todos los documentos los cuales su clave este contenida
	 * en el subconjunto indicado como parámetro
	 * 
	 * @param docs
	 * @return colección de valueObjects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findDocsByName(Collection<String> docs) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos dentro del conjunto "+StringUtils.convertToSQLSet(docs));
		return this.findDocsImpl(IndexOpenHelper.DOC_KEY+" IN "+StringUtils.convertToInterrogativeSQLSet(docs.size()),
				(String[])docs.toArray());
	}
	
	/**
	 * Retorna todos los documentos los cuales su clave este contenida
	 * en el subconjunto indicado como parámetro
	 * 
	 * @param docs
	 * @return colección de valueObjects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findDocsByName(String[] docs) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos dentro del conjunto "+StringUtils.convertToSQLSet(docs));
		return this.findDocsImpl(IndexOpenHelper.DOC_KEY+" IN "+StringUtils.convertToInterrogativeSQLSet(docs.length),
				(String[])docs);
	}
	
	/**
	 * Retorna todos los documentos en los cuales la clave contiene
	 * la cadena de caracteres enviada como parámetro
	 * 
	 * @param doc
	 * @return colección de valueObjects de DOCUMENTO
	 */
	public synchronized Collection<DocumentVO> findDocsLikeName(String doc) {
		Log.i(this.getClass().getSimpleName(), "Busco documentos como "+doc);
		return this.findDocsImpl(IndexOpenHelper.DOC_KEY+" LIKE '"+doc+"%'",
				null);
	}
	
	/**
	 * Remueve un documento identificado por su clave borrando también los
	 * registros que apuntan al mismo en la tabla TERMINO-FRECUENCIA
	 * 
	 * @param doc
	 */
	public synchronized void removeDoc(String doc) {
		Log.i(this.getClass().getSimpleName(), "Remuevo documento "+doc);
		this.getIndex().getDb().delete(IndexOpenHelper.TF_TABLE_NAME, 
				IndexOpenHelper.TF_DOC+" = (SELECT "+IndexOpenHelper.DOC_ID+" FROM "+
					IndexOpenHelper.DOC_TABLE_NAME+" WHERE "+IndexOpenHelper.DOC_KEY+" = ?)", 
				new String[]{doc});
		this.getIndex().getDb().delete(IndexOpenHelper.DOC_TABLE_NAME, 
				IndexOpenHelper.DOC_KEY+" = ?", 
				new String[]{doc});
	}
	
	/**
	 * Remueve un documento identificado por su id borrando también los
	 * registros que apuntan al mismo en la tabla TERMINO-FRECUENCIA
	 * 
	 * @param docId
	 */
	public synchronized void removeDoc(int docId) {
		Log.i(this.getClass().getSimpleName(), "Remuevo documento con id "+docId);
		this.getIndex().getDb().delete(IndexOpenHelper.TF_TABLE_NAME, 
				IndexOpenHelper.TF_DOC+" = ? ", 
				new String[]{String.valueOf(docId)});
		this.getIndex().getDb().delete(IndexOpenHelper.DOC_TABLE_NAME, 
				IndexOpenHelper.DOC_ID+" = ?", 
				new String[]{String.valueOf(docId)});
	}
	
	public synchronized void deleteDoc(String id) {
		this.index.getDb().delete(IndexOpenHelper.DOC_TABLE_NAME, null, null);
	}
	
	//-------------------------------------------------------------------------------------------------------
	//Entidad TERMINO-FRECUENCIA
	//-------------------------------------------------------------------------------------------------------
	
	/**
	 * Inserta una fila de la entidad TERMINO-FRECUENCIA
	 */
	public synchronized void insertTf(int docId,int wordId, BigDecimal tf) {
		
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice tf "+tf.toString()+" de palabra "+String.valueOf(wordId)+" para el documento "+String.valueOf(docId));
		this.getIndex().getDb().execSQL(
				"INSERT INTO "+IndexOpenHelper.TF_TABLE_NAME
					+" ("+IndexOpenHelper.TF_DOC+","+IndexOpenHelper.TF_WORD+","+IndexOpenHelper.TF_VALUE+")"
					+" VALUES("+String.valueOf(docId)+","
							+String.valueOf(wordId)+","
							+tf.toString()+");");
	}
	
	/**
	 * Inserta una fila de la entidad TERMINO-FRECUENCIA con las claves
	 * del TERMINO y DOCUMENTO en vez de su id
	 */
	public synchronized void insertTf(String doc,String word, float tf) {
		Log.i(this.getClass().getSimpleName(), "Se inserta el el índice tf "+String.valueOf(tf)+" de palabra "+word+" para el documento "+doc);
		this.getIndex().getDb().execSQL(
				"INSERT INTO "+IndexOpenHelper.TF_TABLE_NAME
					+" ("+IndexOpenHelper.TF_DOC+","+IndexOpenHelper.TF_WORD+","+IndexOpenHelper.TF_VALUE+")"
					+" VALUES( (SELECT "+IndexOpenHelper.DOC_ID+" FROM "+IndexOpenHelper.DOC_TABLE_NAME
								+" WHERE "+IndexOpenHelper.DOC_KEY+" = '"+doc+"'),"
							+" (SELECT "+IndexOpenHelper.WORD_ID+" FROM "+IndexOpenHelper.WORD_TABLE_NAME
								+" WHERE "+IndexOpenHelper.WORD_KEY+" = '"+word+"'),"
							+String.valueOf(tf)+");");
	}
	
	/**
	 * Método genérico de consulta que retorna varias filas TERMINO-FRECUENCIA
	 * 
	 * @param selection
	 * @param selectionArgs
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	private Collection<TermFrecuencyVO> findTFs(String selection,String[] selectionArgs) {
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.TF_TABLE_NAME, 
				new String[]{IndexOpenHelper.TF_DOC,IndexOpenHelper.TF_WORD,IndexOpenHelper.TF_VALUE}, 
				selection, 
				selectionArgs, 
				null, 
				null, 
				null);
		Collection<TermFrecuencyVO> tfs = new ArrayList<TermFrecuencyVO>();
		TermFrecuencyVO tfVO;
		while (cursor.moveToNext()) {
			tfVO = new TermFrecuencyVO();
			tfVO.setDocument(this.findDocumentById(cursor.getInt(0)));
			tfVO.setWord(this.findWordById(cursor.getInt(1)));
			tfVO.setValue(cursor.getFloat(2));
			tfs.add(tfVO);
		}
		cursor.close();
		return tfs;
	}
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el DOCUMENTO
	 * identificado por el id pasado por parámetro
	 * 
	 * @param docId
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByDocumentId(int docId) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por id de documento "+docId);
		return this.findTFs(IndexOpenHelper.TF_DOC+" = ?", new String[]{String.valueOf(docId)});
	}
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el DOCUMENTO
	 * identificado por la clave pasada por parámetro
	 * 
	 * @param doc
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByDocument(String doc) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por documento "+doc);
		return this.findTFs(IndexOpenHelper.TF_DOC+" = (SELECT "+IndexOpenHelper.DOC_ID+" FROM "+
				IndexOpenHelper.DOC_TABLE_NAME+" WHERE "+IndexOpenHelper.DOC_KEY+" = ?)",
				new String[]{doc});
	}
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el DOCUMENTO
	 * identificado por la clave pasada por parámetro
	 * 
	 * @param doc
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByDocumentAndWords(String doc, String[] words) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por documento "+doc);
		return this.findTFs(IndexOpenHelper.TF_DOC+" = (SELECT "+IndexOpenHelper.DOC_ID+" FROM "+
				IndexOpenHelper.DOC_TABLE_NAME+" WHERE "+IndexOpenHelper.DOC_KEY+" = '"+doc+"' ) AND "+
				IndexOpenHelper.TF_WORD+" IN (SELECT "+IndexOpenHelper.WORD_ID+" FROM "+IndexOpenHelper.WORD_TABLE_NAME +" WHERE "+IndexOpenHelper.WORD_KEY+" IN "+StringUtils.convertToInterrogativeSQLSet(words.length)+")",
				StringUtils.fromObjArr2StrArr(words));
	}
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el TERMINO
	 * identificado por el id pasado por parámetro
	 * 
	 * @param wordId
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByWordId(int wordId) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por id de termino "+wordId);
		return this.findTFs(IndexOpenHelper.TF_WORD+" = ?", new String[]{String.valueOf(wordId)});
	}
	
	/**
	 * Retorna las entidades TERMINO-FRECUENCIA relacionadas con el TERMINO
	 * identificado por la clave pasada por parámetro
	 * 
	 * @param word
	 * @return colección value-objects TERMINO-FRECUENCIA
	 */
	public synchronized Collection<TermFrecuencyVO> findTFsByWord(String word) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencias por termino "+word);
		return this.findTFs(IndexOpenHelper.TF_WORD+" = (SELECT "+IndexOpenHelper.WORD_ID+" FROM "+
				IndexOpenHelper.WORD_TABLE_NAME+" WHERE "+IndexOpenHelper.WORD_KEY+" = ?)",
				new String[]{word});
	}
	
	/**
	 * Retorna una entidad TERMINO-FRECUENCA identificada unívocamente por el par
	 * TERMINO-DOCUMENTO. Se utilizan claves de los mismos
	 * 
	 * @param word
	 * @param doc
	 * @return value-object de TERMINO-FRECUENCIA
	 */
	public synchronized TermFrecuencyVO findTFByWordDocument(String word, String doc) {
		Log.i(this.getClass().getSimpleName(), "Se busca frecuencia por termino "+word+" y documento "+doc);
		Collection<TermFrecuencyVO> coll = 
				this.findTFs(IndexOpenHelper.TF_WORD+" = (SELECT "+IndexOpenHelper.WORD_ID+" FROM "+
							IndexOpenHelper.WORD_TABLE_NAME+" WHERE "+IndexOpenHelper.WORD_KEY+" = ?)"+
							" AND "+
							IndexOpenHelper.TF_DOC+" = (SELECT "+IndexOpenHelper.DOC_ID+" FROM "+
							IndexOpenHelper.DOC_TABLE_NAME+" WHERE "+IndexOpenHelper.DOC_KEY+" = ?)",
				new String[]{word,doc});
		Iterator<TermFrecuencyVO> it = coll.iterator();
		if (it.hasNext()) {
			return it.next();
		} else {
			return null;
		}
	}
	
	/**
	 * Retorna una entidad TERMINO-FRECUENCA identificada unívocamente por el par
	 * TERMINO-DOCUMENTO. Se utilizan ids de los mismos
	 * 
	 * @param word
	 * @param doc
	 * @return value-object de TERMINO-FRECUENCIA
	 */
	public synchronized TermFrecuencyVO findTFByWordDocument(int wordId, int docId) {
		Log.i(this.getClass().getSimpleName(), "Se buscan frecuencia por id de termino "+wordId+" y id de documento "+docId);
		Collection<TermFrecuencyVO> coll = 
				this.findTFs(IndexOpenHelper.TF_WORD+" = ?"+
							" AND "+
							IndexOpenHelper.TF_DOC+" = ?",
				new String[]{String.valueOf(wordId),String.valueOf(docId)});
		Iterator<TermFrecuencyVO> it = coll.iterator();
		if (it.hasNext()) {
			return it.next();
		} else {
			return null;
		}
	}
	
	public synchronized void deleteTf(String id) {
		this.index.getDb().delete(IndexOpenHelper.TF_TABLE_NAME, null, null);
	}
	
	public synchronized Collection<QueryProcessVO> processQuery(int queryId) {
		QueryProcessVO qpVO ;
		Collection<QueryProcessVO> docs = new ArrayList<QueryProcessVO>();
		String sql = "SELECT * FROM "+IndexOpenHelper.QP_VIEW_NAME+" WHERE qry_id = ?;";
		String[] selectionArgs = new String[]{String.valueOf(queryId)};
		Cursor cursor = this.index.getDb().rawQuery(sql, selectionArgs);
		while (cursor.moveToNext()) {
			qpVO = new QueryProcessVO();
			qpVO.setDoc(this.findDocumentById(cursor.getInt(1)));
			qpVO.setValue(cursor.getFloat(2));
			docs.add(qpVO);
		}
		cursor.close();
		return docs;
	}
	
	public synchronized Collection<QueryResultVO> processBoolQuery(String expr) {
		QueryResultVO qrVO ;
		Collection<QueryResultVO> docs = new ArrayList<QueryResultVO>();
		String sql = "SELECT d."+IndexOpenHelper.DOC_ID+", "+expr+" FROM "+IndexOpenHelper.DOC_TABLE_NAME+" d where d."+IndexOpenHelper.DOC_TYPE+" = ?;";
		String[] selectionArgs = new String[]{Constants.DOC_TYPE};
		Cursor cursor = this.index.getDb().rawQuery(sql, selectionArgs);
		while (cursor.moveToNext()) {
			qrVO = new QueryResultVO(this.findDocumentById(cursor.getInt(0)).getName(),cursor.getFloat(1));
			docs.add(qrVO);
		}
		cursor.close();
		return docs;
	}
	

	public synchronized void insertWordFreq(String word, int n) {
		WordVO wordVO = this.findWordByName(word);
		if (wordVO != null) {
			int wordId = wordVO.getId();
			Collection<TermFrecuencyVO> collTfVO = this.findWordFreq(IndexOpenHelper.WORD_F_ID+" = ?", new String[]{String.valueOf(wordId)});
			TermFrecuencyVO tfVO = collTfVO.isEmpty() ? null : collTfVO.iterator().next();
			if (tfVO != null) {
				Log.i(this.getClass().getSimpleName(), "Se acumula la frecuencia "+String.valueOf(n)+" a la palabra "+String.valueOf(wordId));
				ContentValues values = new ContentValues();
				values.put(IndexOpenHelper.WORD_F_VALUE, tfVO.getFreq()+n);
				this.getIndex().getDb().update(IndexOpenHelper.WORD_F_TABLE_NAME,
						values,
						IndexOpenHelper.WORD_F_ID+" = ?", 
						new String[]{String.valueOf(wordId)});
						
			} else {
				Log.i(this.getClass().getSimpleName(), "Se inserta la frecuencua de la palabra "+wordId);
				ContentValues values = new ContentValues();
				values.put(IndexOpenHelper.WORD_F_ID, wordId);
				values.put(IndexOpenHelper.WORD_F_VALUE, n);
				this.getIndex().getDb().insert(IndexOpenHelper.WORD_F_TABLE_NAME, IndexOpenHelper.WORD_F_ID, values);
			}
		}
	}
	
	private Collection<TermFrecuencyVO> findWordFreq(String selection,String[] selectionArgs) {
		Cursor cursor = this.index.getDb().query(IndexOpenHelper.WORD_F_TABLE_NAME, 
				new String[]{IndexOpenHelper.WORD_F_ID,IndexOpenHelper.WORD_F_VALUE}, 
				selection, 
				selectionArgs, 
				null, 
				null, 
				null);
		Collection<TermFrecuencyVO> tfs = new ArrayList<TermFrecuencyVO>();
		TermFrecuencyVO tfVO;
		while (cursor.moveToNext()) {
			tfVO = new TermFrecuencyVO();
			tfVO.setWord(this.findWordById(cursor.getInt(0)));
			tfVO.setFreq(cursor.getInt(1));
			tfs.add(tfVO);
		}
		cursor.close();
		return tfs;
	}
	
	public synchronized void deleteWordFreq(String id) {
		this.index.getDb().delete(IndexOpenHelper.WORD_F_TABLE_NAME, null, null);
	}
	
}
 